﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.Runtime.Serialization;
using K3R.Data;
using K3R.Core;
using System.Linq;
using System.Collections.Generic;

namespace K3R.Email {
	public interface IEmailMessage : ITextReplace {
		#region Properties
		Guid UniqueIdentifier { get; }
		int Type { get; }
		string TypeName { get; set; }
		string Address { get; }
		bool Sending { get; }
		bool Sent { get; }
		bool Cancelled { get; }
		bool Actioned { get; }
		#endregion

		#region Mark As
		bool MarkAsSending();
		bool MarkAsNotSending();
		bool MarkAsSent();
		bool MarkAsCancelled();
		bool MarkAsActioned();
		#endregion

		#region List
		IEnumerable<IEmailMessage> List();
		#endregion
	}

	[DataContract]
	public class EmailMessage : IEmailMessage {
		#region Properties
		[DataMember(Name = "guid")] public Guid UniqueIdentifier { get; protected set; }
		[DataMember(Name = "type")] public int Type { get; protected set; }
		[DataMember(Name = "typeName")] public string TypeName { get; set; }
		[DataMember(Name = "address")] public string Address { get; protected set; }
		[DataMember(Name = "emailGuid")] public Guid EmailGuid { get; protected set; }
		[DataMember(Name = "sending")] public bool Sending { get; protected set; }
		[DataMember(Name = "sent")] public bool Sent { get; protected set; }
		[DataMember(Name = "cancelled")] public bool Cancelled { get; protected set; }
		[DataMember(Name = "actioned")] public bool Actioned { get; protected set; }
		#endregion

		#region Constructors
		public EmailMessage() { }
		#endregion

		#region Instance
		public virtual bool MarkAsSending() {
			return Sending = EmailMessage.UpdateSending(UniqueIdentifier, true);
		}

		public virtual bool MarkAsNotSending() {
			bool done = EmailMessage.UpdateSending(UniqueIdentifier, false);
			if (done)
				Sending = false;

			return done;
		}

		public virtual bool MarkAsSent() {
			if (Sending)
				MarkAsNotSending();
			return Sent = EmailMessage.UpdateSent(UniqueIdentifier, true);
		}

		public virtual bool MarkAsCancelled() {
			if (Sending)
				MarkAsNotSending();
			return Cancelled = EmailMessage.UpdateCancelled(UniqueIdentifier, true);
		}

		public virtual bool MarkAsActioned() {
			bool done = EmailMessage.UpdateActioned(UniqueIdentifier, true);
			if (done)
				Actioned = true;

			return done;
		}

		public virtual IEnumerable<IEmailMessage> List() {
			return from email in EmailMessage.List(null, null, null)
				   select (IEmailMessage)email;
		}
		#endregion

		#region Protected Static
		protected static Guid Add(int type, string address, Guid emailGuid, SqlParameter[] parameters) {
			Guid guid = Guid.Empty;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueAdd")) {
				conn.AddParameter("@Type", type);
				conn.AddParameter("@Email", address);
				conn.AddParameter("@EmailGUID", emailGuid);

				if (parameters != null)
					conn.AddParameter(parameters);

				SqlParameter guidParam = conn.AddOutputGUID();

				try {
					conn.ExecuteNonQuery();
					guid = guidParam.DBValueOrDefault<Guid>();
				}
				catch (SqlException err) {
					switch (err.Number) {
						default:
							throw err;
					}
				}
			}
			return guid;
		}

		protected static bool Edit(Guid guid, int type, string address, Guid emailGuid, SqlParameter[] parameters) {
			bool done = false;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueEdit")) {
				conn.AddParameter("@GUID", guid);
				conn.AddParameter("@Type", type);
				conn.AddParameter("@Email", address);
				conn.AddParameter("@EmailGUID", emailGuid);

				if (parameters != null)
					conn.AddParameter(parameters);

				switch (conn.ExecuteNonQuery()) {
					case 1:
						done = true;
						break;
					case 0:
						throw new EmailNotFoundException();
				}
			}
			return done;
		}

		protected static bool UpdateSending(Guid guid, bool? sending, SqlParameter[] parameters) {
			bool done = false;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueSending")) {
				conn.AddParameter("@GUID", guid);
				conn.AddParameter("@Sending", sending);

				if (parameters != null)
					conn.AddParameter(parameters);

				switch (conn.ExecuteNonQuery()) {
					case 1:
						done = true;
						break;
					case 0:
						throw new EmailNotFoundException();
				}
			}
			return done;
		}

		protected static bool UpdateSent(Guid guid, bool? sent, SqlParameter[] parameters) {
			bool done = false;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueSent")) {
				conn.AddParameter("@GUID", guid);
				conn.AddParameter("@Sent", sent);

				if (parameters != null)
					conn.AddParameter(parameters);

				switch (conn.ExecuteNonQuery()) {
					case 1:
						done = true;
						break;
					case 0:
						throw new EmailNotFoundException();
				}
			}
			return done;
		}

		protected static bool UpdateCancelled(Guid guid, bool? cancelled, SqlParameter[] parameters) {
			bool done = false;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueCancelled")) {
				conn.AddParameter("@GUID", guid);
				conn.AddParameter("@Cancelled", cancelled);

				if (parameters != null)
					conn.AddParameter(parameters);

				switch (conn.ExecuteNonQuery()) {
					case 1:
						done = true;
						break;
					case 0:
						throw new EmailNotFoundException();
				}
			}
			return done;
		}

		protected static bool UpdateActioned(Guid guid, bool? actioned, SqlParameter[] parameters) {
			bool done = false;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueActioned")) {
				conn.AddParameter("@GUID", guid);
				conn.AddParameter("@Actioned", actioned);

				if (parameters != null)
					conn.AddParameter(parameters);

				switch (conn.ExecuteNonQuery()) {
					case 1:
						done = true;
						break;
					case 0:
						throw new EmailNotFoundException();
				}
			}
			return done;
		}

		protected static Guid GetEmailGuid(Guid guid, SqlParameter[] parameters) {
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueEmailGuid")) {
				conn.AddParameter("@GUID", guid);

				if (parameters != null)
					conn.AddParameter(parameters);

				SqlParameter emailGuidParameter = conn.AddParameter("@EmailGUID", SqlDbType.UniqueIdentifier, ParameterDirection.Output);

				try {
					conn.ExecuteNonQuery();
				}
				catch (SqlException err) {
					switch (err.Number) {
						case 50001:
							throw new EmailAlreadyActionedException(err);
						case 50002:
							throw new EmailCancelledException(err);
						default:
							throw err;
					}
				}
				return emailGuidParameter.DBValueOrDefault<Guid>();
			}
		}
		#endregion

		#region Static
		public static Guid Add(int type, string address, Guid emailGuid) {
			return Add(type, address, emailGuid, null);
		}

		public static Guid Add(EmailType type, string address, Guid emailGuid) {
			return Add((int)type, address, emailGuid);
		}

		public static bool Edit(Guid guid, int type, string address, Guid emailGuid) {
			return Edit(guid, type, address, emailGuid, null);
		}

		public static bool Edit(Guid guid, EmailType type, string address, Guid emailGuid) {
			return Edit(guid, (int)type, address, emailGuid);
		}

		public static bool UpdateSending(Guid guid, bool? sending) {
			return UpdateSending(guid, sending, null);
		}

		public static bool UpdateSent(Guid guid, bool? sent) {
			return UpdateSent(guid, sent, null);
		}

		public static bool UpdateCancelled(Guid guid, bool? cancelled) {
			return UpdateCancelled(guid, cancelled, null);
		}

		public static bool UpdateActioned(Guid guid, bool? actioned) {
			return UpdateActioned(guid, actioned, null);
		}

		public static EmailMessage Load(Guid guid) {
			EmailMessage message = null;
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueLoad")) {
				conn.AddParameter("@GUID", guid);

				SqlDataReader dr = conn.ExecuteReader(CommandBehavior.SingleRow);
				if (dr.Read())
					message = Reader(dr);
				else throw new EmailNotFoundException();
			}
			return message;
		}

		public static List<EmailMessage> List(bool? sending, bool? sent, bool? cancelled) {
			using (Connection conn = new Connection(K3R.Settings.ConnectionString, "EmailQueueList")) {
				conn.AddParameter("@Sending", sending);
				conn.AddParameter("@Sent", sent);
				conn.AddParameter("@Cancelled", cancelled);

				return ListReader(conn.ExecuteReader());
			}
		}

		public static Guid GetEmailGuid(Guid guid) {
			return GetEmailGuid(guid, null);
		}
		#endregion

		#region Reader
		private static EmailMessage Reader(SqlDataReader dr) {
			return new EmailMessage {
				UniqueIdentifier = (Guid)dr["GUID"],
				Type = (int)dr["Type"],
				TypeName = dr["TypeName"].DBValueOrDefault<string>(),
				Address = dr["Email"].DBValueOrDefault<string>(),
				EmailGuid = dr["EmailGUID"].DBValueOrDefault<Guid>(),
				Sending = (bool)dr["Sending"],
				Sent = dr["Sent"].DBValueOrDefault<bool>(),
				Cancelled = dr["Cancelled"].DBValueOrDefault<bool>(),
				Actioned = dr["Actioned"].DBValueOrDefault<bool>()
			};
		}

		private static List<EmailMessage> ListReader(SqlDataReader dr) {
			List<EmailMessage> list = new List<EmailMessage>();

			while (dr.Read())
				list.Add(Reader(dr));

			return list;
		}
		#endregion

		#region ITextReplace Members
		public virtual string TextReplace(string source) {
			return source
				.Replace("##guid##", UniqueIdentifier.ToString())
				.Replace("##url##", Settings.DefaultUrl);
		}
		#endregion
	}
}
